//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
// (c) Philipp Peshin (phil@mobilae.ru), 2003
//
// 'Edit nodes' tool.
//

# include "StdAfx.h"
# include "Resource.h"
# include "Tool.h"
# include "AddPoint.h"
# include "DeletePoint.h"
# include "MovePoint.h"
# include "EditPoint.h"
# include "SetRGNode.h"
# include "SplitPolyline.h"
# include "ReversePoints.h"
# include "ClosePolyline.h"
# include "GeneralizeRgn.h"
# include "RectifyPolygon.h"
# include "RefreshHoles.h"
# include "EditElement.h"
# include "NodeProperties.h"
# include "Types.h"
# include "MapView.h"
# include "Load.h"
# include "WebMap.h"
# include "Selection.h"
# include "Position.h"
# include "Menu.h"
# include "Label.h"
# include "Scale.h"
# include "Levels.h"
# include "DrawHelpers.h"
# include "Globals.h"

class CNodesTool : public CTool {
  public:
	CNodesTool () : CTool (toolEditNodes) {}

	// Override.
	virtual bool Init ();
	virtual void Destroy ();

	virtual void OnActivate ();

	virtual void OnLDown   (int _x, int _y, DWORD _dwKeys);
	virtual void OnLUp     (int _x, int _y, DWORD _dwKeys);
	virtual void OnMove    (int _x, int _y, DWORD _dwKeys);
	virtual void OnRUp     (int _x, int _y, DWORD _dwKeys);
	virtual void OnLDblClk (int _x, int _y, DWORD _dwKeys);

	virtual void OnPaint (HDC _hDC, const RECT & _rect);

	virtual void OnCtxMenu (int _x, int _y);

	virtual bool OnCommand (WORD _wID);

  private:
	bool m_bStick;
	point_t m_pointStick;

	rgn_elements_t::iterator m_iElement;
	size_t m_cPointIndex;

	point_t m_pointNew;
	bool m_bShowNewPoint;

	bool _FindPoint    (int _x, int _y);
	bool _FindNewPoint (int _x, int _y);
};

DECLARE_TOOL (CNodesTool);

const int c_nNodeBoxSize = 3;

static HCURSOR g_hcurMovePointMode;
static HCURSOR g_hcurMovePointDrag;
static HCURSOR g_hcurMovePoint;
static HCURSOR g_hcurInsertPoint;

static HPEN g_hpenStick;
static HPEN g_hpenNoStick;
static HPEN g_hpenPoint;
static HPEN g_hpenRubber;

bool CNodesTool::Init () {
	m_bStick        = false;
	m_bShowNewPoint = false;

	g_hpenStick   = ::CreatePen (PS_SOLID, 1, c_colCyan);
	g_hpenNoStick = ::CreatePen (PS_SOLID, 1, c_colGray);
	g_hpenPoint   = ::CreatePen (PS_SOLID, 1, c_colYellow);
	g_hpenRubber  = ::CreatePen (PS_SOLID, 1, c_colGray);

	g_hcurMovePointMode = ::LoadCursor (g_hInst, MAKEINTRESOURCE (IDC_CursorMovePointMode));
	g_hcurMovePoint     = ::LoadCursor (g_hInst, MAKEINTRESOURCE (IDC_CursorMovePoint));
	g_hcurMovePointDrag = ::LoadCursor (g_hInst, MAKEINTRESOURCE (IDC_CursorMovePointDrag));
	g_hcurInsertPoint   = ::LoadCursor (g_hInst, MAKEINTRESOURCE (IDC_CursorInsertPoint));

	return true;
}

void CNodesTool::Destroy () {
	if (g_hpenStick)
		::DeleteObject (g_hpenStick);
	if (g_hpenNoStick)
		::DeleteObject (g_hpenNoStick);
	if (g_hpenPoint)
		::DeleteObject (g_hpenPoint);
	if (g_hpenRubber)
		::DeleteObject (g_hpenRubber);
}

void CNodesTool::OnActivate () {
	SetPermanentCursor (g_hcurMovePointMode);

	m_bStick        = false;
	m_bShowNewPoint = false;
}

bool CNodesTool::_FindPoint (int _x, int _y) {
	if (g_Selection.size () != 1 || ! g_Selection [0].pLayer)
		return false;

	rgn_t & rgn = * g_Selection [0].iRgn;
	for (rgn_elements_t::iterator i = rgn.elements.begin (); i != rgn.elements.end (); ++ i) {
		rgn_element_t & element = * i;
		if (element.bDeleted)
			continue;
		if (! LevelIsVisible (element.cLevel, rgn.uchEndLevel))
			continue;

		const points_t & points = element.points;
		const size_t cPoints = points.size ();
		for (size_t cPoint = 0; cPoint < cPoints; ++ cPoint) {
			const point_t & point = points [cPoint];

			const long lX = XToScreen (point.x);
			const long lY = YToScreen (point.y);
			if (::labs (_x - lX) <= 3 && ::labs (_y - lY) <= 3) {
				m_iElement    = i;
				m_cPointIndex = cPoint;
				return true;
			}
		}
	}

	return false;
}

bool CNodesTool::_FindNewPoint (int _x, int _y) {
	if (g_Selection.size () != 1 || ! g_Selection [0].pLayer)
		return false;

	const size_t cLevelIdx = GetZoomLevel (g_fScale);

	const float c_fE = c_fE_in_pixels/g_kY;
    point_t current;
    ScreenToPt (_x, _y, current);

	rgn_t & rgn = * g_Selection [0].iRgn;
	for (rgn_elements_t::iterator i = rgn.elements.begin (); i != rgn.elements.end (); ++ i) {
		rgn_element_t & element = * i;
		if (element.bDeleted)
			continue;
		if (! LevelIsVisible (element.cLevel, rgn.uchEndLevel))
			continue;

		const points_t & points = element.points;
        if (PolylineContainsPt (element.points, current, c_fE, g_map.fCosY, rgn.IsPolygon (), & m_pointNew, & m_cPointIndex)) {
			g_map.pTypeSet->pfRoundPoint (g_map, cLevelIdx, m_pointNew);
			m_iElement = i;
			return true;
        }
	}

	return false;
}

void CNodesTool::OnLDown (int _x, int _y, DWORD _dwKeys) {
	const bool bStillContains = _FindPoint (_x, _y);

	if (! bStillContains) {
		SelectObjectAt (_x, _y, false, false, false);
		if (g_Selection.size () == 1 && g_Selection [0].pLayer && g_Selection [0].iRgn->IsWTR ())
			// NOTE: don't edit waypoints and tracks.
			g_Selection.clear ();
		Redraw ();
		UpdateMenu ();
	} else
		StartDND (_x, _y);
}

static
void _RedrawNewPoint (const point_t & _pointPrev, const point_t & _pointNext) {
	const int x0 = XToScreen (_pointPrev.x);
	const int y0 = YToScreen (_pointPrev.y);

	const int x1 = XToScreen (_pointNext.x);
	const int y1 = YToScreen (_pointNext.y);
		
	RECT rect;

	if (x0 < x1) {
		rect.left = x0;
		rect.right = x1;
	} else {
		rect.right = x0;
		rect.left = x1;
	}

	if (y0 < y1) {
		rect.top = y0;
		rect.bottom = y1;
	} else {
		rect.bottom = y0;
		rect.top = y1;
	}
	::InflateRect (& rect, c_iCrossSize + 1, c_iCrossSize + 1);

	Redraw (& rect);
}

void CNodesTool::OnLUp (int _x, int _y, DWORD _dwKeys) {
	SetPermanentCursor (g_hcurMovePointMode);

	if (! CTool::s_bDND)
		return;
	StopDND ();

	// Apply changes.
	if (g_Selection.size () == 1 && g_Selection [0].pLayer && ! g_Selection [0].iRgn->IsWTR ()) {
		const long lXOffset = CTool::s_lX_DND - CTool::s_lX_DND_Start;
		const long lYOffset = CTool::s_lY_DND - CTool::s_lY_DND_Start;
		if (lXOffset == 0 && lYOffset == 0)
			return;
        if (
			::labs (lXOffset) <= 2*::GetSystemMetrics (SM_CXDOUBLECLK) &&
			::labs (lYOffset) <= 2*::GetSystemMetrics (SM_CYDOUBLECLK) &&
			::GetTickCount () - CTool::s_dwTime_DND_Start <= ::GetDoubleClickTime ()
		) {
			Redraw ();
			return;
		}

		// NOTE: this may change _x & _y.
		point_t pointNew;
		if (m_iElement->cLevel < g_map.Levels.size ()) 
			StickPointToNodeOrGrid (m_iElement->cLevel, _x, _y, pointNew, & (* g_Selection [0].iRgn));
		else
			ScreenToPt (_x, _y, pointNew);
		rgn_t & rgn = * g_Selection [0].iRgn;
		MovePoint (& rgn, m_iElement - rgn.elements.begin (), m_cPointIndex, pointNew);
	}
}

void CNodesTool::OnMove (int _x, int _y, DWORD _dwKeys) {
	if (! CTool::s_bDND) {
        if (g_Selection.size () == 1 && g_Selection [0].pLayer && ! g_Selection [0].iRgn->IsWTR ()) {
            if (_FindPoint (_x, _y))
			    SetPermanentCursor (g_hcurMovePoint);
            else {
				// NOTE: _FindNewPoint() affects m_pointNew.
				const point_t pointPrevNew = m_pointNew;
				if (_FindNewPoint (_x, _y)) {
					SetPermanentCursor (g_hcurInsertPoint);

					// Show "insert point" feedback.
					m_bShowNewPoint = true;
					m_bStick        = false;
					_RedrawNewPoint (pointPrevNew, m_pointNew);

					return;
				} else
					SetPermanentCursor (g_hcurMovePointMode);
            }
		} else
			SetPermanentCursor (g_hcurMovePointMode);

		// Erase "insert point" feedback, if need.
		if (m_bShowNewPoint) {
			m_bShowNewPoint = false;
			_RedrawNewPoint (m_pointNew, m_pointNew);
		}
	} else {
	    SetPermanentCursor (g_hcurMovePointDrag);

	    // NOTE: this may change _x & _y.
		if (m_iElement->cLevel < g_map.Levels.size ())
			m_bStick = StickPointToNodeOrGrid (m_iElement->cLevel, _x, _y, m_pointStick, & (* g_Selection [0].iRgn));
		else {
			ScreenToPt (_x, _y, m_pointStick);
			m_bStick = false;
		}

	    CTool::s_lX_DND = _x;
	    CTool::s_lY_DND = _y;

	    // TODO: optimize rect
	    Redraw ();
    }
}

struct CCrossData {
	long lY;
	bool bFirst;
	CCrossData (long _lY, bool _bFirst) : lY (_lY), bFirst (_bFirst) {}
};

void CNodesTool::OnPaint (HDC _hDC, const RECT & _rect) {
	//
	// Highlight points of selected object.
	//

	if (g_Selection.size () != 1 || ! g_Selection [0].pLayer || g_Selection [0].iRgn->IsWTR ())
		return;
	const rgn_t & rgn = * g_Selection [0].iRgn;

	// Prepare the list of unique pairs (X, Y) as map: X -> (Y, bFirst).
	typedef std::multimap<long, CCrossData> crosses_t;
	crosses_t crosses;
	for (rgn_elements_t::const_iterator i = rgn.elements.begin (); i != rgn.elements.end (); ++ i) {
		const rgn_element_t & element = * i;
		if (element.bDeleted)
			continue;
		if (! LevelIsVisible (element.cLevel, rgn.uchEndLevel))
			continue;

		const points_t & points = element.points;
		const size_t cPoints = points.size ();
		for (size_t cPoint = 0; cPoint < cPoints; ++ cPoint) {
			const point_t & point = points [cPoint];

			const long lX = XToScreen (point.x);
			if (lX < _rect.left || lX >= _rect.right)
				continue;

			const long lY = YToScreen (point.y);
			if (lY < _rect.top || lY >= _rect.bottom)
				continue;

			const bool bFirst = cPoint == 0;

			const std::pair<crosses_t::iterator, crosses_t::iterator> cr = crosses.equal_range (lX);
			if (cr.first == cr.second)
				crosses.insert (crosses_t::value_type (lX, CCrossData (lY, bFirst)));
			else {
				bool bInsert = true;
				for (crosses_t::iterator ic = cr.first; ic != cr.second; ++ ic) {
					// Make sure that (X, Y) is unique pair.
					if (ic->second.lY == lY) {
						if (bFirst)
							// NOTE: first point has higher priority to draw.
							ic->second.bFirst = true;

						bInsert = false;
						break;
					}
				}

				if (bInsert)
					crosses.insert (cr.first, crosses_t::value_type (lX, CCrossData (lY, bFirst)));
			}
		}
	}

	// Draw crosses.
	::SetROP2 (_hDC, R2_XORPEN);
	::SelectObject (_hDC, g_hpenPoint);

	bool bPrevFirst = false;
	for (crosses_t::iterator ic = crosses.begin (); ic != crosses.end (); ++ ic) {
		const long lX = ic->first;
		const long lY = ic->second.lY;
		const bool bFirst = ic->second.bFirst;

		if (bFirst != bPrevFirst) {
			// Highlight the first point by different color.
			::SelectObject (_hDC, bFirst ? g_hpenGrayThin : g_hpenPoint);
			bPrevFirst = bFirst;
		}

		DrawCross (_hDC, lX, lY);
	}

	//
	// Draw stick point or new point.
	//
	if (! CTool::s_bDND && ! m_bShowNewPoint)
		return;

	const long x = XToScreen (m_bShowNewPoint ? m_pointNew.x : m_pointStick.x);
	const long y = YToScreen (m_bShowNewPoint ? m_pointNew.y : m_pointStick.y);

	if (m_bStick) {
		::SetROP2 (_hDC, R2_COPYPEN);
		::SelectObject (_hDC, g_hpenBlackThin);
		::SelectObject (_hDC, g_hbrRed);
		DrawSquare (_hDC, x, y, c_nNodeBoxSize);
	} 

	::SetROP2 (_hDC, R2_XORPEN);
	::SelectObject (_hDC, m_bStick ? g_hpenStick : g_hpenNoStick);
	DrawCross (_hDC, x, y);

	//	
	// Draw 'rubber'.
	//
 	if (! CTool::s_bDND)
		return;

	if (m_iElement->points.size () >= 2) {
		assert (m_cPointIndex < m_iElement->points.size ());

		::SelectObject (_hDC, g_hpenRubber);
		::SetROP2 (_hDC, R2_XORPEN);

		size_t cPrevPointIndex = m_bShowNewPoint ? m_cPointIndex : m_cPointIndex - 1;
		if (m_cPointIndex == 0 && g_Selection [0].iRgn->IsPolygon ())
			cPrevPointIndex = m_iElement->points.size () - 1;
		if (cPrevPointIndex < m_iElement->points.size ()) {
			const point_t & point_p = m_iElement->points [cPrevPointIndex];
			const long lX_p = XToScreen (point_p.x);
			const long lY_p = YToScreen (point_p.y);

			::MoveToEx (_hDC, CTool::s_lX_DND, CTool::s_lY_DND, NULL);
			::LineTo   (_hDC, lX_p, lY_p);
		}

		size_t cNextPointIndex = m_cPointIndex + 1;
		if (m_cPointIndex >= m_iElement->points.size () - 1 && g_Selection [0].iRgn->IsPolygon ())
			cNextPointIndex = 0;
		if (cNextPointIndex < m_iElement->points.size ()) {
			const point_t & point_n = m_iElement->points [cNextPointIndex];
			const long lX_n = XToScreen (point_n.x);
			const long lY_n = YToScreen (point_n.y);

			::MoveToEx (_hDC, CTool::s_lX_DND, CTool::s_lY_DND, NULL);
			::LineTo   (_hDC, lX_n, lY_n);
		}
	}
}

static long g_lCtxX;
static long g_lCtxY;

void CNodesTool::OnRUp (int _x, int _y, DWORD _dwKeys) {
	CNodesTool::OnCtxMenu (_x, _y);
}

void CNodesTool::OnCtxMenu (int _x, int _y) {
	if (g_bLoading)
		return;

	g_lCtxX = _x;
	g_lCtxY = _y;

	const HMENU hPopupMenu = LoadMenu (IDM_POPUP);
	CMenuHolder hPopupMenu_Holder (hPopupMenu);

	HMENU hMenu = NULL;

	if (! g_Selection.empty ()) {
		const find_info_t & Sel0 = g_Selection [0];
		if (! Sel0.pLayer || Sel0.iRgn->IsWTR ())
			// NOTE: waypoints and tracks are not editable.
			return;

		const size_t cLevel = GetZoomLevel (g_fScale);
		const bool bGeneralize = cLevel != -1 && cLevel < g_map.Levels.size ();

		if (_FindPoint (_x, _y)) {
			hMenu = ::GetSubMenu (hPopupMenu, smpEditNode);
			::SetMenuDefaultItem (hMenu, ID_NODE_PROPERTIES, MF_BYCOMMAND);
			assert (hMenu);

			assert (g_Selection.size () == 1 && Sel0.pLayer);
			const rgn_t & rgn = * Sel0.iRgn;
			const size_t cElement = m_iElement - rgn.elements.begin ();
			const bool cLevelIs0 = m_iElement->cLevel == 0;
			const bool bIsPolyline = rgn.IsPolyline ();
			const bool bIsRoad = bIsPolyline && g_map.pTypeSet->pfIsRoad (rgn);

			const bool bCanDelete = rgn.IsPolygon () ?
				m_iElement->points.size () >= 4 :
				m_iElement->points.size () >= 3;
			const bool bCanSplit      = bIsPolyline && 0 < m_cPointIndex && m_cPointIndex < m_iElement->points.size () - 1;
			const bool bCanReverse    = bIsPolyline;
			const bool bCanConnect    = bIsRoad && cLevelIs0 && CanConnectNodes    (& rgn, cElement, m_cPointIndex);
			const bool bCanDisconnect = bIsPolyline          && CanDisconnectNodes (& rgn, cElement, m_cPointIndex);

			::EnableMenuItem (hMenu, ID_EDIT_DELETE_NODE,      (bCanDelete     ? MF_ENABLED : MF_GRAYED) | MF_BYCOMMAND);
			::EnableMenuItem (hMenu, ID_EDIT_CONNECT_NODES,    (bCanConnect    ? MF_ENABLED : MF_GRAYED) | MF_BYCOMMAND);
			::EnableMenuItem (hMenu, ID_EDIT_DISCONNECT_NODES, (bCanDisconnect ? MF_ENABLED : MF_GRAYED) | MF_BYCOMMAND);
			::EnableMenuItem (hMenu, ID_EDIT_SPLIT_POLYLINE,   (bCanSplit      ? MF_ENABLED : MF_GRAYED) | MF_BYCOMMAND);
			::EnableMenuItem (hMenu, ID_EDIT_REVERSE_NODES,    (bCanReverse    ? MF_ENABLED : MF_GRAYED) | MF_BYCOMMAND);
			::EnableMenuItem (hMenu, ID_EDIT_GENERALIZE,       (bGeneralize    ? MF_ENABLED : MF_GRAYED) | MF_BYCOMMAND);

			if (! bIsPolyline) {
				::DeleteMenu (hMenu, ID_EDIT_CONNECT_NODES,    MF_BYCOMMAND);
				::DeleteMenu (hMenu, ID_EDIT_DISCONNECT_NODES, MF_BYCOMMAND);
				::DeleteMenu (hMenu, ID_EDIT_REVERSE_NODES,    MF_BYCOMMAND);
				::DeleteMenu (hMenu, ID_EDIT_SPLIT_POLYLINE,   MF_BYCOMMAND);
				DeleteDuplicatedMenuSeparators (hMenu);
			}
		} else if (_FindNewPoint (_x, _y)) {
			hMenu = ::GetSubMenu (hPopupMenu, smpEditLine);
			::SetMenuDefaultItem (hMenu, ID_LINE_PROPERTIES, MF_BYCOMMAND);
			assert (hMenu);

			assert (g_Selection.size () == 1 && Sel0.pLayer);
			const rgn_t & rgn = * Sel0.iRgn;
			const points_t & points = m_iElement->points;
			const size_t cPoints = points.size ();
			const bool bIsPolyline = rgn.IsPolyline ();
			const bool bIsPolygon  = rgn.IsPolygon ();

			const bool bCanAdd       = true;
			const bool bCanClose     = bIsPolyline && cPoints >= 3 && ! (points [0] == points [cPoints - 1]);
			const bool bCanReverse   = bIsPolyline;
			const bool bRefreshHoles = bIsPolygon;
			const bool bCanRectify   = bIsPolygon;

			::EnableMenuItem (hMenu, ID_EDIT_ADD_NODE,       (bCanAdd       ? MF_ENABLED : MF_GRAYED) | MF_BYCOMMAND);
			::EnableMenuItem (hMenu, ID_EDIT_REVERSE_NODES,  (bCanReverse   ? MF_ENABLED : MF_GRAYED) | MF_BYCOMMAND);
			::EnableMenuItem (hMenu, ID_EDIT_GENERALIZE,     (bGeneralize   ? MF_ENABLED : MF_GRAYED) | MF_BYCOMMAND);
			::EnableMenuItem (hMenu, ID_EDIT_REFRESH_HOLES,  (bRefreshHoles ? MF_ENABLED : MF_GRAYED) | MF_BYCOMMAND);
			::EnableMenuItem (hMenu, ID_EDIT_CLOSE_POLYLINE, (bCanClose     ? MF_ENABLED : MF_GRAYED) | MF_BYCOMMAND);
			::EnableMenuItem (hMenu, ID_EDIT_POLYGON_RECTIFY, (bCanRectify  ? MF_ENABLED : MF_GRAYED) | MF_BYCOMMAND);

			if (! bIsPolyline) {
				::DeleteMenu (hMenu, ID_EDIT_REVERSE_NODES,   MF_BYCOMMAND);
				::DeleteMenu (hMenu, ID_EDIT_CLOSE_POLYLINE,  MF_BYCOMMAND);
			}
			if (! bIsPolygon) {
				::DeleteMenu (hMenu, ID_EDIT_REFRESH_HOLES,   MF_BYCOMMAND);
				::DeleteMenu (hMenu, ID_EDIT_POLYGON_RECTIFY, MF_BYCOMMAND);
			}
			DeleteDuplicatedMenuSeparators (hMenu);
		}
	}

	if (hMenu == NULL && g_bShowGoogleMaps) {
		hMenu = ::GetSubMenu (hPopupMenu, smpGoogle);
		::EnableMenuItem (hMenu, ID_GM_REFRESH_TILE, (CanRefreshWebMapTile () ? MF_ENABLED : MF_GRAYED) | MF_BYCOMMAND);
	}

	if (hMenu)
		PopupMenu (hMenu);
}

bool CNodesTool::OnCommand (WORD _wID) {
	switch (_wID) {
		case ID_EDIT_ADD_NODE: {
			if (g_Selection.size () != 1 || ! g_Selection [0].pLayer)
				return false;
			rgn_t & rgn = * g_Selection [0].iRgn;
			AddPoint (& rgn, m_iElement - rgn.elements.begin (), m_cPointIndex, m_pointNew);
			return true;
		}

		case ID_EDIT_DELETE_NODE: {
			if (! _FindPoint (g_lCtxX, g_lCtxY))
				return false;
			if (g_Selection.size () != 1 || ! g_Selection [0].pLayer)
				return false;
			rgn_t & rgn = * g_Selection [0].iRgn;
			DeletePoint (& rgn, m_iElement - rgn.elements.begin (), m_cPointIndex);
			return true;
		}

		case ID_EDIT_CONNECT_NODES: {
			if (! _FindPoint (g_lCtxX, g_lCtxY))
				return false;
			if (g_Selection.size () != 1 || ! g_Selection [0].pLayer)
				return false;
			rgn_t & rgn = * g_Selection [0].iRgn;
			ConnectNodes (& rgn, m_iElement - rgn.elements.begin (), m_cPointIndex);
			return true;
		}

		case ID_EDIT_DISCONNECT_NODES: {
			if (! _FindPoint (g_lCtxX, g_lCtxY))
				return false;
			if (g_Selection.size () != 1 || ! g_Selection [0].pLayer)
				return false;
			rgn_t & rgn = * g_Selection [0].iRgn;
			DisconnectNodes (& rgn, m_iElement - rgn.elements.begin (), m_cPointIndex);
			return true;
		}

		case ID_EDIT_SPLIT_POLYLINE: {
			if (g_Selection.size () != 1 || ! g_Selection [0].pLayer || m_cPointIndex == 0 || m_cPointIndex >= m_iElement->points.size () - 1)
				return false;
			rgn_t & rgn = * g_Selection [0].iRgn;
			SplitPolyline (& rgn, m_iElement - rgn.elements.begin (), m_cPointIndex);
			Refresh ();
			return true;
		}

		case ID_EDIT_REVERSE_NODES: {
			if (g_Selection.size () != 1 || ! g_Selection [0].pLayer)
				return false;
			rgn_t & rgn = * g_Selection [0].iRgn;
			ReversePoints (& rgn, m_iElement - rgn.elements.begin ());
			Refresh ();
			return true;
		}

		case ID_EDIT_CLOSE_POLYLINE: {
			if (g_Selection.size () != 1 || ! g_Selection [0].pLayer)
				return false;
			rgn_t & rgn = * g_Selection [0].iRgn;
			ClosePolyline (& rgn, m_iElement - rgn.elements.begin ());
			Refresh ();
			return true;
		}

		case ID_EDIT_POLYGON_RECTIFY: {
			if (g_Selection.size () != 1 || ! g_Selection [0].pLayer)
				return false;
			rgn_t & rgn = * g_Selection [0].iRgn;
			RectifyPolygon (rgn);
			Refresh ();
			return true;
		}

		case ID_EDIT_REFRESH_HOLES: {
			if (g_Selection.size () != 1 || ! g_Selection [0].pLayer)
				return false;
			rgn_t & rgn = * g_Selection [0].iRgn;
			RefreshHoles (& rgn);
			Refresh ();
			return true;
		}

		case ID_EDIT_GENERALIZE: {
			if (g_Selection.size () != 1 || ! g_Selection [0].pLayer)
				return false;
			const size_t cLevel = GetZoomLevel (g_fScale);
			if (cLevel >= g_map.Levels.size ())
				return false;
			rgn_t & rgn = * g_Selection [0].iRgn;
			GeneralizeRgnInLevel (rgn, cLevel);
			Refresh ();
			return true;
		}

		case ID_NODE_PROPERTIES: {
			if (g_Selection.size () != 1 || ! g_Selection [0].pLayer || m_cPointIndex >= m_iElement->points.size ())
				return false;

			rgn_t & rgn = * g_Selection [0].iRgn;
			ShowNodeProperties (g_hWnd, rgn, m_iElement - rgn.elements.begin (), m_cPointIndex);
			return true;
		}

		case ID_LINE_PROPERTIES: {
			if (g_Selection.size () != 1 || ! g_Selection [0].pLayer)
				return false;
			const size_t cLevel = GetZoomLevel (g_fScale);
			if (cLevel >= g_map.Levels.size ())
				return false;
			rgn_t & rgn = * g_Selection [0].iRgn;
			EditElementPoints (g_hWnd, & rgn, m_iElement - rgn.elements.begin ());
			return true;
		}

		case ID_GM_REFRESH_TILE: RefreshWebMapTile (g_lCtxX, g_lCtxY); break;
		case ID_GM_SHIFT:        ShiftWebMap (g_hWnd); break;
		case ID_GM_RESET_SHIFT:  ResetWebMapShift (); break;
	}

	return false;
}

void CNodesTool::OnLDblClk (int _x, int _y, DWORD _dwKeys) {
	if (g_Selection.empty ())
		return;

	const find_info_t & Sel0 = g_Selection [0];
	if (! Sel0.pLayer || Sel0.iRgn->IsWTR ())
		// NOTE: waypoints and tracks are not editable.
		return;

	if (_FindPoint (_x, _y)) {
		CNodesTool::OnCommand (ID_NODE_PROPERTIES);
	} else if (_FindNewPoint (_x, _y)) {
		CNodesTool::OnCommand (ID_LINE_PROPERTIES);
    }
}
